package com.zf.fastexcel.utils;

import cn.idev.excel.FastExcel;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.event.AnalysisEventListener;
import cn.idev.excel.read.metadata.holder.ReadRowHolder;
import cn.idev.excel.write.handler.CellWriteHandler;
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
import com.zf.fastexcel.entity.TemplateFieldTest;
import com.zf.fastexcel.utils.handler.CustomSheetNameWriteHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.core.io.ClassPathResource;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Excel模板工具：模板中可以定义各种单元格格式，不需要在代码中进行繁琐的设置，更加方便
 */
public class ExcelTemplateUtils {
    public static final String FIELD_PATTERN = "^\\s*\\{.+\\}\\s*$";


    /**
     * 获取 Excel填充模板中的各个属性及其位置信息
     *
     * @param templateInputStream 模板文件的流
     * @return rowIndex -> columnIndex -> fieldName
     */
    public static Map<Integer, Map<Integer, String>> getFieldInTemplate(InputStream templateInputStream) {
        ReadTemplateListener templateListener = new ReadTemplateListener();
        FastExcel.read(templateInputStream)
                .sheet(0)
                .headRowNumber(0)
                .registerReadListener(templateListener)
                .doRead();
        return templateListener.getFieldPositionMap();
    }

    /**
     * 根据模板导出数据，可以根据传入的 参考数据 参数，填充的数据设置批注
     *
     * @param templateInputStream 模板的输入流
     * @param fillData            填充模板的数据
     * @param referObject         参数数据，如果需要对比 填充数据 与 参考数据 的不同并且要生成批注则可以传入该对象，否则传入 null
     */
    public static byte[] exportToByteWithTemplate(InputStream templateInputStream, Object fillData, Object referObject) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int length;
        while ((length = templateInputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, length);
        }
        // 模板中的属性的位置信息:  rowIndex -> columnIndex -> fieldName
        Map<Integer, Map<Integer, String>> fieldInTemplate = getFieldInTemplate(new ByteArrayInputStream(outputStream.toByteArray()));

        // 和参考对象的差异：属性名 -> 差异值
        Map<String, String> fieldDiffMap = ObjectDiffCompareUtils.compareBaseFieldDiff(fillData, referObject);

        ByteArrayOutputStream res = new ByteArrayOutputStream();
        FastExcel.write(res)
                .withTemplate(new ByteArrayInputStream(outputStream.toByteArray()))
                .registerWriteHandler(new CommentWriteHandler(fieldInTemplate, fieldDiffMap))
                .sheet(0)
                // .sheet(0, "new-name") 这样是无法修改sheet的名字的，还是会使用模板sheet的名字
                // 缩影自定义处理器来修改填充后sheet的名字，默认使用模板sheet的名字
                .registerWriteHandler(new CustomSheetNameWriteHandler(0, "新名字"))
                .doFill(fillData);

        return res.toByteArray();
    }


    /**
     * 读取模板的监听器
     */
    @Getter
    private static class ReadTemplateListener extends AnalysisEventListener<Map<Integer, Object>> {
        /**
         * 模板中的属性的位置信息:  rowIndex -> columnIndex -> fieldName
         */
        private final Map<Integer, Map<Integer, String>> fieldPositionMap = new HashMap<>();

        @Override
        public void invokeHead(Map headMap, AnalysisContext context) {
        }


        @Override
        public void invoke(Map<Integer, Object> map, AnalysisContext analysisContext) {
            ReadRowHolder rowHolder = analysisContext.readRowHolder();
            // 行位置
            Integer rowIndex = rowHolder.getRowIndex();
            // 获取改行的位置MAP
            Map<Integer, String> rowFiledMap = fieldPositionMap.get(rowIndex);
            if (Objects.isNull(rowFiledMap)) {
                rowFiledMap = new HashMap<>();
                fieldPositionMap.put(rowIndex, rowFiledMap);
            }
            // 遍历改行的列，获取没有需要填充的属性所在的列
            for (Map.Entry<Integer, Object> entry : map.entrySet()) {
                Integer columnIndex = entry.getKey();
                Object obj = entry.getValue();
                String cellContent = Objects.nonNull(obj) ? String.valueOf(obj) : "";
                // 单元格中是否有需要填充的属性，格式: {属性名}
                if (Pattern.matches(FIELD_PATTERN, cellContent)) {
                    String fieldName = StringUtils.trim(cellContent.replaceAll("(^\\s*\\{)|(\\}\\s*$)", ""));
                    rowFiledMap.put(columnIndex, fieldName);
                }
            }
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {

        }
    }

    /**
     * 批注处理器
     */
    @Slf4j
    private static class CommentWriteHandler implements CellWriteHandler {

        /**
         * 模板中的属性的位置信息:  rowIndex -> columnIndex -> fieldName
         */
        private final Map<Integer, Map<Integer, String>> fieldInTemplate;
        /**
         * 需要设置批注的属性及批注的值：属性名 -> 差异值
         */
        private final Map<String, String> fieldDiffMap;

        public CommentWriteHandler(Map<Integer, Map<Integer, String>> fieldInTemplate, Map<String, String> fieldDiffMap) {
            this.fieldInTemplate = Optional.ofNullable(fieldInTemplate).orElse(Collections.emptyMap());
            this.fieldDiffMap = Optional.ofNullable(fieldDiffMap).orElse(Collections.emptyMap());
        }


        /**
         * 每个的单元格的所有操作完成后都会执行该方法
         */
        @Override
        public void afterCellDispose(CellWriteHandlerContext context) {
            Cell cell = context.getCell();
            // 单元格所在的行列
            Integer rowIndex = context.getRowIndex();
            Integer columnIndex = context.getColumnIndex();

            // 获取是否是被填充数据的单元格
            String fieldName = Optional.ofNullable(fieldInTemplate.get(rowIndex))
                    .map(item -> item.get(columnIndex))
                    .orElse("");

            // 如果不是包含属性的单元格，则结束操作
            if (StringUtils.isBlank(fieldName)) {
                return;
            }
            // 如果不需要生成批注，则结束操作
            if (!fieldDiffMap.containsKey(fieldName)) {
                return;
            }
            // 获取批注内容
            String commentContent = MapUtils.getString(fieldDiffMap, fieldName, "");

            Sheet sheet = context.getWriteSheetHolder().getSheet();
            // 创建绘图
            Drawing<?> drawingPatriarch = sheet.createDrawingPatriarch();
            // 创建锚点：设置锚点的展示大小范围
            // XSSFClientAnchor的后四位参数：左上角所在的列行坐标(col1,row1),右下角所在的列行坐标(col2,row2)，这个框内就是批注的大小范围，当锚点被用来设置批注时，只有大小范围有效，批注位置都是在单元格右上角
            // XSSFClientAnchor的前四位参数，用来对后四位参数的微调：左上角坐标列行偏移量(dx1,dy1),右下角坐标列行偏移量(dx2,dy2)，一般不需要偏移，都设置为0
            // XSSFClientAnchor(500*255, 500*255, 1000*255, 1000*255, columnIndex, rowIndex, columnIndex + 2, rowIndex + 1);
            XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, columnIndex, rowIndex, columnIndex + 2, rowIndex + 1);
            // 在此锚点处创建一个批注对象
            Comment comment = drawingPatriarch.createCellComment(anchor);
            // 写入批注信息
            comment.setString(new XSSFRichTextString("参考值：" + commentContent));
            // 将批注添加到单元格对象中
            cell.setCellComment(comment);

            // 给设置了批注的单元格 填充背景颜色
            CellStyle cellStyle = context.getWriteWorkbookHolder().getWorkbook().createCellStyle();
            cellStyle.cloneStyleFrom(cell.getCellStyle());
            cellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cell.setCellStyle(cellStyle);
        }
    }


    public static void main(String[] args) throws IOException {
        TemplateFieldTest fillData = new TemplateFieldTest();
        fillData.setName("张三");
        fillData.setTest("测试1");
        fillData.setAge(12);
        fillData.setTitle("标题1");
        TemplateFieldTest referData = new TemplateFieldTest();
        referData.setName("李四");
        referData.setTest("测试2");
        referData.setAge(12);
        referData.setTitle("标题2");

        byte[] bytes = exportToByteWithTemplate(new ClassPathResource("template/field_get_template.xlsx").getInputStream(), fillData, referData);
        Files.write(Paths.get("D:\\结果.xlsx"), bytes);
    }
}
